P6563 [SBCOI2020] 一直在你身旁
一道非常值得做的单调队列优化DP,刚开始看到这道题目一直想着二分,愣是没有看出是个DP,后来仔细想了下,可以轻易的写出 O(n3) 的暴力.
定义 fi,j 表示确定区间 [i,j] 内的数字所需的最小花费,得出转移方程
fi,j=min(l≤k<rmax(fl,k,fk+1,r)+ak)
显然是个区间DP
通过标签观察式子有点类似 fk+ak 的形式,似乎可以优化,首先不好直接对这个式子下手,可以分类讨论一下,把其中的 max 去掉
仔细思考一下,可以发现 fi,j≤fi,j+1,fi,j≥fi+1,j
那么也就是说,我们在分类讨论的时候只需要找到一个最小的 x 使得 fl,x>fx+1,r 就行了
对于 fl,k>fk+1,r 的情况
fl,r=l≤k<rminfl,k+ak,其中 fl,k+ak 是单调不减的,所以答案直接为 fl,x+ax
对于 fl,k<fk+1,r 的情况就稍微比较麻烦了
fl,r=l≤k<rmin(fk+1,r+ak),每次用单调队列维护一个最小值就行了.
所以时间复杂度就可以优化成 O(n2)
代码
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N = 7105; typedef long long ll;
ll f[N][N]; int a[N], q[N];
int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= n; i++) f[i][i] = 0, f[i][i + 1] = a[i]; for (int r = 2; r <= n; r++) { int front = 0, tail = -1, p = r - 1; q[0] = r - 1; for (int l = r - 2; l >= 1; l--) { while (p >= l && f[l][p] > f[p + 1][r])//找到最小的x p--; f[l][r] = f[l][p + 1] + a[p + 1];//这里主要需要加一 if (front <= tail && q[front] > p)//第二种情况 front++; if (front <= tail) f[l][r] = min(f[l][r], f[q[front] + 1][r] + a[q[front]]); while (front <= tail && f[q[tail] + 1][r] + a[q[tail]] >= f[l + 1][r] + a[l]) tail--; q[++tail] = l; } } printf("%lld\n", f[1][n]); } return 0; }
|